/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.behavior;

import com.google.common.base.Strings;
import com.je.bpm.core.model.*;
import com.je.bpm.core.model.event.BoundaryEvent;
import com.je.bpm.core.model.process.SubProcess;
import com.je.bpm.core.model.process.Transaction;
import com.je.bpm.core.model.task.KaiteBaseUserTask;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.ActivitiIllegalArgumentException;
import com.je.bpm.engine.delegate.DelegateExecution;
import com.je.bpm.engine.delegate.DelegateHelper;
import com.je.bpm.engine.impl.bpmn.helper.ScopeUtil;
import com.je.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import com.je.bpm.engine.impl.cmd.SubmitTypeEnum;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntityImpl;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntityManager;
import com.je.bpm.engine.impl.util.CollectionUtil;
import com.je.bpm.engine.upcoming.UpcomingCommentInfoDTO;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * 并行多实例行为
 */
public class ParallelMultiInstanceBehavior extends MultiInstanceActivityBehavior {

    private static final long serialVersionUID = 1L;

    public ParallelMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior originalActivityBehavior) {
        super(activity, originalActivityBehavior);
    }

    /**
     * Handles the parallel case of spawning the instances. Will create child executions accordingly for every instance needed.耽耽耽
     */
    @Override
    protected int createInstances(DelegateExecution execution) {
        String assigneeUser = getAssigneeUser(execution);
        //处理人员
        setLoopVariable(execution, MultiInstanceActivityBehavior.PROCESSING_USERS_INFO, Arrays.asList(assigneeUser.split(",")));
        int nrOfInstances = resolveNrOfInstances(execution);
        if (nrOfInstances < 0) {
            throw new ActivitiIllegalArgumentException("Invalid number of instances: must be non-negative integer value" + ", but was " + nrOfInstances);
        }
        //添加审批告知
        addApprovalNotice(Context.getCommandContext(), execution, null, null, assigneeUser);
        execution.setMultiInstanceRoot(true);
        //添加会签节点需要用的变量
        addCounterVariable((ExecutionEntityImpl) execution);
        setLoopVariable(execution, NUMBER_OF_INSTANCES, nrOfInstances);
        setLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES, 0);
        setLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES, nrOfInstances);


        List<DelegateExecution> concurrentExecutions = new ArrayList<DelegateExecution>();
        for (int loopCounter = 0; loopCounter < nrOfInstances; loopCounter++) {
            DelegateExecution concurrentExecution = Context.getCommandContext().getExecutionEntityManager().createChildExecution((ExecutionEntity) execution);
            concurrentExecution.setCurrentFlowElement(activity);
            concurrentExecution.setActive(true);
            concurrentExecution.setScope(false);
            concurrentExecutions.add(concurrentExecution);
            logLoopDetails(concurrentExecution, "initialized", loopCounter, 0, nrOfInstances, nrOfInstances);
        }

        // Before the activities are executed, all executions MUST be created up front
        // Do not try to merge this loop with the previous one, as it will lead
        // to bugs, due to possible child execution pruning.// 这里运行3
        for (int loopCounter = 0; loopCounter < nrOfInstances; loopCounter++) {
            DelegateExecution concurrentExecution = concurrentExecutions.get(loopCounter);
            // executions can be inactive, if instances are all automatics
            // (no-waitstate) and completionCondition has been met in the meantime
            if (concurrentExecution.isActive() && !concurrentExecution.isEnded() && concurrentExecution.getParent().isActive() && !concurrentExecution.getParent().isEnded()) {
                setLoopVariable(concurrentExecution, getCollectionElementIndexVariable(), loopCounter);
                executeOriginalBehavior(concurrentExecution, loopCounter);
            }
        }

        // See ACT-1586: ExecutionQuery returns wrong results when using multi
        // instance on a receive task The parent execution must be set to false, so it wouldn't show up in
        // the execution query when using .activityId(something). Do not we cannot nullify the
        // activityId (that would have been a better solution), as it would break boundary event behavior.
        if (!concurrentExecutions.isEmpty()) {
            ExecutionEntity executionEntity = (ExecutionEntity) execution;
            executionEntity.setActive(false);
        }

        return nrOfInstances;
    }

    /**
     * Called when the wrapped {@link com.je.bpm.engine.impl.delegate.ActivityBehavior} calls the {@link AbstractBpmnActivityBehavior} method. Handles the completion of one of the parallel instances
     */
    @Override
    public void leave(DelegateExecution execution) {

        //实例数为零
        boolean zeroNrOfInstances = false;
        if (resolveNrOfInstances(execution) == 0) {
            // Empty collection, just leave.
            zeroNrOfInstances = true;
            removeLocalLoopVariable(execution, getCollectionElementIndexVariable());
            super.leave(execution); // Plan the default leave
            execution.setMultiInstanceRoot(false);
        }

        int loopCounter = getLoopVariable(execution, getCollectionElementIndexVariable());
        //多实例总数
        int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES);
        //已完成实例的数量
        DelegateExecution miRootExecution = getMultiInstanceRootExecution(execution);
        int nrOfCompletedInstances = getLoopVariable(miRootExecution, NUMBER_OF_COMPLETED_INSTANCES) + 1;
        //当前活动的实例数，即尚未完成的实例数。对于串行多实 例，这个值总是1。
        int nrOfActiveInstances = getLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES) - 1;
        String type = getStringVariable(execution, COUNTERSIGN_PASS_TYPE);
        Boolean isCounter = (!Strings.isNullOrEmpty(type)) ? true : false;
        if (isCounter) {
            buildCounterInfo(execution, miRootExecution);
        }
        Context.getCommandContext().getHistoryManager().recordActivityEnd((ExecutionEntity) execution, null);
        callActivityEndListeners(execution);

        if (zeroNrOfInstances) {
            return;
        }

        if (miRootExecution != null) { // will be null in case of empty collection
            setLoopVariable(miRootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances);
            setLoopVariable(miRootExecution, NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances);
            if (isCounter) {
                setCounterInfo(miRootExecution);
            }
        }
        updateResultCollection(execution, miRootExecution);

        //executeCompensationBoundaryEvents(execution.getCurrentFlowElement(), execution);

        logLoopDetails(execution, "instance completed", loopCounter, nrOfCompletedInstances, nrOfActiveInstances, nrOfInstances);
        ExecutionEntity executionEntity = (ExecutionEntity) execution;
        if (executionEntity.getParent() != null) {
            executionEntity.inactivate();
            lockFirstParentScope(executionEntity);
            //通过逻辑
            if (nrOfCompletedInstances >= nrOfInstances || completionConditionSatisfied(miRootExecution)) {
                //具体流向哪里
                countersigned(nrOfInstances, executionEntity, execution);
            }
        } else {
            dispatchActivityCompletedEvent(executionEntity);
            removeLocalLoopVariable(execution, getCollectionElementIndexVariable());
            execution.setMultiInstanceRoot(false);
            super.leave(execution);
        }
    }


    public void countersigned(int nrOfInstances, ExecutionEntity executionEntity, DelegateExecution execution) {
        ExecutionEntity executionToUse = null;
        if (nrOfInstances > 0) {
            executionToUse = executionEntity.getParent();
        } else {
            executionToUse = executionEntity;
        }
        propagateLoopDataOutputRefToProcessInstance(executionToUse);
        boolean hasCompensation = false;
        Activity activity = (Activity) execution.getCurrentFlowElement();
        if (activity instanceof Transaction) {
            hasCompensation = true;
        } else if (activity instanceof SubProcess) {
            SubProcess subProcess = (SubProcess) activity;
            for (FlowElement subElement : subProcess.getFlowElements()) {
                if (subElement instanceof Activity) {
                    Activity subActivity = (Activity) subElement;
                    if (CollectionUtil.isNotEmpty(subActivity.getBoundaryEvents())) {
                        for (BoundaryEvent boundaryEvent : subActivity.getBoundaryEvents()) {
                            if (CollectionUtil.isNotEmpty(boundaryEvent.getEventDefinitions()) &&
                                    boundaryEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
                                hasCompensation = true;
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (hasCompensation) {
            ScopeUtil.createCopyOfSubProcessExecutionForCompensation(executionToUse);
        }

        if (activity instanceof CallActivity) {
            ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();
            if (executionToUse != null) {
                List<String> callActivityExecutionIds = new ArrayList<String>();

                // Find all execution entities that are at the call activity
                List<ExecutionEntity> childExecutions = executionEntityManager.collectChildren(executionToUse);
                if (childExecutions != null) {
                    for (ExecutionEntity childExecution : childExecutions) {
                        if (activity.getId().equals(childExecution.getCurrentActivityId())) {
                            callActivityExecutionIds.add(childExecution.getId());
                        }
                    }

                    // Now all call activity executions have been collected, loop again and check which should be removed
                    for (int i = childExecutions.size() - 1; i >= 0; i--) {
                        ExecutionEntity childExecution = childExecutions.get(i);
                        if (StringUtils.isNotEmpty(childExecution.getSuperExecutionId())
                                && callActivityExecutionIds.contains(childExecution.getSuperExecutionId())) {

                            executionEntityManager.deleteProcessInstanceExecutionEntity(childExecution.getId(), activity.getId(),
                                    "call activity completion condition met", true, true);
                        }
                    }

                }
            }
        }

        deleteChildExecutions(executionToUse, false, Context.getCommandContext());
        removeLocalLoopVariable(executionToUse, getCollectionElementIndexVariable());
        executionToUse.setScope(false);
        executionToUse.setMultiInstanceRoot(false);
        SubmitTypeEnum submitTypeEnum = null;
        ProcessEngineConfigurationImpl processEngineConfiguration = Context.getProcessEngineConfiguration();
        BpmnModel bpmnModel = processEngineConfiguration.getRepositoryService().getBpmnModel(execution.getProcessDefinitionId());
        //否决退回
        if (!isPass(execution.getParent())) {
            submitTypeEnum = SubmitTypeEnum.VETO;
            List<Map<String, String>> nodeInfo = getNodeInfo(execution, bpmnModel);
            Map<String, String> map = nodeInfo.get(0);
            String directTask = map.get("directTaskId");
            String prevAssignee = map.get("prevAssignee");

            FlowElement targetFlowElement = bpmnModel.getFlowElement(directTask);
            if (!(targetFlowElement instanceof KaiteBaseUserTask)) {
                throw new ActivitiException("Only kaite user task can goback.");
            }
            KaiteBaseUserTask targetBaseUserTask = (KaiteBaseUserTask) targetFlowElement;
            getCommandContext().addAttribute("isVeto", true);
            Map<String, Object> transientVariables = new HashMap<>();
            transientVariables.put(directTask, prevAssignee);
            executionToUse.setTransientVariables(transientVariables);
            executionToUse.setCurrentFlowElement(targetFlowElement);
            dispatchActivityCompletedEvent(executionEntity);
            //会签多人否决
            //添加审批告知
            String submitType = SubmitTypeEnum.VETO.getName();
            setVar(execution, SubmitTypeEnum.VETO, submitType);
            //如果目标任务属于多实例节点，则要根据多实例要求创建多个任务（串行创建一个，并行创建多个）
            setDirectTaskIdVar(executionToUse);
            leaveRemoveCounterVar(execution.getParent());
            if (targetBaseUserTask.hasMultiInstanceLoopCharacteristics()) {
                Context.getAgenda().planContinueMultiInstanceOperation(executionToUse);
            } else {
                Context.getAgenda().planContinueProcessOperation(executionToUse);
            }
        } else {
            submitTypeEnum = SubmitTypeEnum.PASS;
            //会签多人通过
            //添加审批告知
            String submitType = SubmitTypeEnum.PASS.getName();
            setVar(execution, SubmitTypeEnum.PASS, submitType);
            //正常通过流转
            dispatchActivityCompletedEvent(executionEntity);
            setDirectTaskIdVar(executionToUse);
            leaveRemoveCounterVar(execution.getParent());
            Context.getAgenda().planTakeOutgoingSequenceFlowsOperation(executionToUse, true);
        }
        Map<String, Object> bean = null;
        Object variable = Context.getCommandContext().getAttribute(KaiteBaseUserTaskActivityBehavior.UPCOMINGINFO);
        if (variable != null && variable instanceof UpcomingCommentInfoDTO) {
            UpcomingCommentInfoDTO upcomingCommentInfoDTO = (UpcomingCommentInfoDTO) variable;
            bean = upcomingCommentInfoDTO.getBean();
        }
        Context.getCommandContext().addAttribute(KaiteBaseUserTaskActivityBehavior.UPCOMINGINFO, DelegateHelper.buildUpcomingInfo(bean, "",
                submitTypeEnum, execution.getProcessInstanceBusinessKey(), "", null));
    }

    public void lockFirstParentScope(DelegateExecution execution) {

        ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();

        boolean found = false;
        ExecutionEntity parentScopeExecution = null;
        ExecutionEntity currentExecution = (ExecutionEntity) execution;
        while (!found && currentExecution != null && currentExecution.getParentId() != null) {
            parentScopeExecution = executionEntityManager.findById(currentExecution.getParentId());
            if (parentScopeExecution != null && parentScopeExecution.isScope()) {
                found = true;
            }
            currentExecution = parentScopeExecution;
        }

        parentScopeExecution.forceUpdate();
    }

    // TODO: can the ExecutionManager.deleteChildExecution not be used?
    protected void deleteChildExecutions(ExecutionEntity parentExecution, boolean deleteExecution, CommandContext commandContext) {
        // Delete all child executions
        ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();
        Collection<ExecutionEntity> childExecutions = executionEntityManager.findChildExecutionsByParentExecutionId(parentExecution.getId());
        if (CollectionUtil.isNotEmpty(childExecutions)) {
            for (ExecutionEntity childExecution : childExecutions) {
                deleteChildExecutions(childExecution, true, commandContext);
            }
        }

        if (deleteExecution) {
            executionEntityManager.cancelExecutionAndRelatedData(parentExecution, "Multi-instance complete condition expression passed");
        }
    }

}
